package com.slowfly.rule.engine.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.slowfly.rule.engine.bean.CacheBean;
import com.slowfly.rule.engine.component.RedisComponent;
import com.slowfly.rule.engine.constants.IRiskConstants;
import com.slowfly.rule.engine.entity.Rule;
import com.slowfly.rule.engine.entity.Strategy;
import com.slowfly.rule.engine.enums.NodeTypeEnum;
import com.slowfly.rule.engine.enums.OptEnum;
import com.slowfly.rule.engine.mapper.RuleMapper;
import com.slowfly.rule.engine.model.Condition;
import com.slowfly.rule.engine.model.Flow;
import com.slowfly.rule.engine.model.Operator;
import com.slowfly.rule.engine.param.RuleParam;
import com.slowfly.rule.engine.result.PageResult;
import com.slowfly.rule.engine.result.RuleResult;
import com.slowfly.rule.engine.service.IRuleService;
import com.slowfly.rule.engine.service.IStrategyService;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.slowfly.rule.engine.util.PageResultConvert;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author shalongfei
 * @since 2024-03-06
 */
@Slf4j
@Service
public class RuleServiceImpl extends ServiceImpl<RuleMapper, Rule> implements IRuleService {

    @Autowired
    private IStrategyService strategyService;
    @Autowired
    private RedisComponent redisComponent;

    private Map<Integer, Rule> ruleCache;

    public void setCache() {
        Map<Integer, Rule> ruleCache = Maps.newConcurrentMap();
        for (Rule rule : list()) {
            ruleCache.put(rule.getId(), rule);
        }
        this.ruleCache = ruleCache;
    }

    public void editCache(int type, int id) {
        if (type == OptEnum.ADD.getCode() || type == OptEnum.EDIT.getCode()) {
            ruleCache.put(id, getById(id));
        } else if (type == OptEnum.DELETE.getCode()) {
            ruleCache.remove(id);
        }
    }

    public Rule get(Integer id) {
        return ruleCache.get(id);
    }

    public String getScripts(Rule rule) {
        if (Objects.isNull(rule.getContent()) || CollectionUtils.isEmpty(rule.getContent().getConditions())) {
            return IRiskConstants.DEFAULT_RULE;
        }
        StringBuilder builder = new StringBuilder();
        for (Condition condition : rule.getContent().getConditions()) {
            builder.append(rule.getContent().isAnd() ? " && " : " || ").append(getScripts(condition));
        }
        return builder.substring(4);
    }

    private String getScripts(Condition condition) {
        if (condition.isSimple()) {
            return Operator.createScript(condition);
        }
        StringBuilder builder = new StringBuilder();
        for (Condition con : condition.getConditions()) {
            builder.append(condition.getAnd() ? " && " : " || ").append(getScripts(con));
        }
        return "(" + builder.substring(4) + ")";
    }

    public Set<Integer> getFeatureIds(Rule rule) {
        Set<Integer> set = Sets.newHashSet();
        if (Objects.isNull(rule.getContent()) || CollectionUtils.isEmpty(rule.getContent().getConditions())) {
            return set;
        }
        for (Condition condition : rule.getContent().getConditions()) {
            set.addAll(getFeatureIds(condition));
        }
        return set;
    }

    private Set<Integer> getFeatureIds(Condition condition) {
        Set<Integer> set = Sets.newHashSet();
        if (condition.isSimple()) {
            set.add(condition.getFeatureId());
            if (Operator.isField(condition.getOperator())) {
                set.add(Integer.parseInt(condition.getValue()));
            }
        } else {
            for (Condition con : condition.getConditions()) {
                set.addAll(getFeatureIds(con));
            }
        }
        return set;
    }

    public List<RuleResult> listRule() {
        return BeanUtil.copyToList(list(), RuleResult.class);
    }

    public PageResult<RuleResult> pageRule(RuleParam param) {
        LambdaQueryWrapper<Rule> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Objects.nonNull(param.getId()), Rule::getId, param.getId());
        queryWrapper.like(StringUtils.isNotBlank(param.getName()), Rule::getName, param.getName());
        queryWrapper.orderByDesc(Rule::getUpdateTime);
        Page<Rule> page = page(new Page<>(param.getPageNo(), param.getPageSize()), queryWrapper);
        return PageResultConvert.convert(page, RuleResult.class);
    }

    public boolean addRule(RuleParam param) {
        Rule rule = new Rule();
        BeanUtils.copyProperties(param, rule);
        boolean saved = save(rule);
        redisComponent.publish(IRiskConstants.RULE_CHANEL, new CacheBean(OptEnum.ADD.getCode(), rule.getId()));
        return saved;
    }

    public boolean editRule(RuleParam param) {
        Rule rule = new Rule();
        BeanUtils.copyProperties(param, rule);
        boolean updated = updateById(rule);
        redisComponent.publish(IRiskConstants.RULE_CHANEL, new CacheBean(OptEnum.EDIT.getCode(), param.getId()));
        return updated;
    }

    public boolean deleteRule(RuleParam param) {
        List<Strategy> strategies = strategyService.list();
        for (Strategy strategy : strategies) {
            checkFlows(strategy.getId(), param.getId(), strategy.getFlows());
        }
        boolean removed = removeById(param.getId());
        redisComponent.publish(IRiskConstants.RULE_CHANEL, new CacheBean(OptEnum.DELETE.getCode(), param.getId()));
        return removed;
    }

    private void checkFlows(Integer strategyId, Integer ruleId, List<Flow> flows) {
        if (CollectionUtils.isEmpty(flows)) {
            return;
        }
        for (Flow flow : flows) {
            if (Objects.equals(flow.getNodeType(), NodeTypeEnum.RULE.getCode())
                    && Objects.equals(flow.getNodeId(), ruleId)) {
                throw new RuntimeException(String.format("该规则已经被策略%s引用，请先解除引用!", strategyId));
            }
            checkFlows(strategyId, ruleId, flow.getChildren());
        }
    }

    public boolean checkDeleteFeature(Integer featureId) {
        for (Rule rule : list()) {
            Set<Integer> featureIds = getFeatureIds(rule);
            if (featureIds.contains(featureId)) {
                throw new RuntimeException(String.format("该特征已经被规则%s引用，请先解除引用!", rule.getId()));
            }
        }
        return true;
    }
}
